<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>第6章 FTL 综述 | ShuangChenYue</title>
    <meta name="generator" content="VuePress 1.9.10">
    <link rel="icon" href="https://cdn.jsdelivr.net/gh/cmty256/imgs-blog@main/logo/白云.38zbldnhh180.jpg">
    <meta name="description" content="满招损，谦受益">
    <meta name="keywords" content="专注于Cpp语言的旅行者">
    
    <link rel="preload" href="/assets/css/0.styles.952d6952.css" as="style"><link rel="preload" href="/assets/js/app.67adcfd9.js" as="script"><link rel="preload" href="/assets/js/4.9aaa1650.js" as="script"><link rel="preload" href="/assets/js/1.5474518c.js" as="script"><link rel="preload" href="/assets/js/3.593d14fc.js" as="script"><link rel="preload" href="/assets/js/132.1c04cde5.js" as="script"><link rel="prefetch" href="/assets/js/10.3242746b.js"><link rel="prefetch" href="/assets/js/100.9224de43.js"><link rel="prefetch" href="/assets/js/101.f0d1b059.js"><link rel="prefetch" href="/assets/js/102.996bfc6d.js"><link rel="prefetch" href="/assets/js/103.9bfdbd6f.js"><link rel="prefetch" href="/assets/js/104.8613f283.js"><link rel="prefetch" href="/assets/js/105.aa6e809e.js"><link rel="prefetch" href="/assets/js/106.90192392.js"><link rel="prefetch" href="/assets/js/107.e82a40b7.js"><link rel="prefetch" href="/assets/js/108.994cd438.js"><link rel="prefetch" href="/assets/js/109.ec15acc2.js"><link rel="prefetch" href="/assets/js/11.c04b41c1.js"><link rel="prefetch" href="/assets/js/110.c32d8576.js"><link rel="prefetch" href="/assets/js/111.453b5d50.js"><link rel="prefetch" href="/assets/js/112.ffbdb3a4.js"><link rel="prefetch" href="/assets/js/113.12b8ad7d.js"><link rel="prefetch" href="/assets/js/114.899d2998.js"><link rel="prefetch" href="/assets/js/115.b7ad9576.js"><link rel="prefetch" href="/assets/js/116.a8394748.js"><link rel="prefetch" href="/assets/js/117.0edfe25b.js"><link rel="prefetch" href="/assets/js/118.9161b1fe.js"><link rel="prefetch" href="/assets/js/119.be59e21b.js"><link rel="prefetch" href="/assets/js/12.41437bf6.js"><link rel="prefetch" href="/assets/js/120.bcf439fb.js"><link rel="prefetch" href="/assets/js/121.c3d251b8.js"><link rel="prefetch" href="/assets/js/122.62b1caba.js"><link rel="prefetch" href="/assets/js/123.787c2ab0.js"><link rel="prefetch" href="/assets/js/124.a880746f.js"><link rel="prefetch" href="/assets/js/125.d8edfe7b.js"><link rel="prefetch" href="/assets/js/126.4ff01546.js"><link rel="prefetch" href="/assets/js/127.9416d1ff.js"><link rel="prefetch" href="/assets/js/128.01a4a7a0.js"><link rel="prefetch" href="/assets/js/129.76876665.js"><link rel="prefetch" href="/assets/js/13.922328e9.js"><link rel="prefetch" href="/assets/js/130.7f631dd9.js"><link rel="prefetch" href="/assets/js/131.c9e0fde9.js"><link rel="prefetch" href="/assets/js/133.e8f381cd.js"><link rel="prefetch" href="/assets/js/134.03d19f8b.js"><link rel="prefetch" href="/assets/js/135.44607494.js"><link rel="prefetch" href="/assets/js/136.6a1eb3c9.js"><link rel="prefetch" href="/assets/js/137.27898fd1.js"><link rel="prefetch" href="/assets/js/138.5bc0cf54.js"><link rel="prefetch" href="/assets/js/139.c2d1addc.js"><link rel="prefetch" href="/assets/js/14.e54d7526.js"><link rel="prefetch" href="/assets/js/140.052ec8e4.js"><link rel="prefetch" href="/assets/js/141.131abb5a.js"><link rel="prefetch" href="/assets/js/142.6ba6c07b.js"><link rel="prefetch" href="/assets/js/143.5dd51d22.js"><link rel="prefetch" href="/assets/js/144.b45afca8.js"><link rel="prefetch" href="/assets/js/145.faa9fb04.js"><link rel="prefetch" href="/assets/js/146.b54c024d.js"><link rel="prefetch" href="/assets/js/147.a1223242.js"><link rel="prefetch" href="/assets/js/148.4767bcb2.js"><link rel="prefetch" href="/assets/js/149.b65ab046.js"><link rel="prefetch" href="/assets/js/15.7082a3da.js"><link rel="prefetch" href="/assets/js/150.9bd8c175.js"><link rel="prefetch" href="/assets/js/151.9f830e96.js"><link rel="prefetch" href="/assets/js/152.41cde7f0.js"><link rel="prefetch" href="/assets/js/153.f57d65e0.js"><link rel="prefetch" href="/assets/js/154.5d7c8d51.js"><link rel="prefetch" href="/assets/js/155.0ae99532.js"><link rel="prefetch" href="/assets/js/156.5a54e043.js"><link rel="prefetch" href="/assets/js/157.c25b5d40.js"><link rel="prefetch" href="/assets/js/158.aa025b46.js"><link rel="prefetch" href="/assets/js/159.47939d88.js"><link rel="prefetch" href="/assets/js/16.fc775b7b.js"><link rel="prefetch" href="/assets/js/160.f8624459.js"><link rel="prefetch" href="/assets/js/161.7a075dc2.js"><link rel="prefetch" href="/assets/js/162.1d48f266.js"><link rel="prefetch" href="/assets/js/163.5d68a99f.js"><link rel="prefetch" href="/assets/js/164.1262d0e5.js"><link rel="prefetch" href="/assets/js/165.2ccf0bdd.js"><link rel="prefetch" href="/assets/js/166.21ece4d9.js"><link rel="prefetch" href="/assets/js/167.bf8adb95.js"><link rel="prefetch" href="/assets/js/168.1cb8440d.js"><link rel="prefetch" href="/assets/js/169.1dd1e396.js"><link rel="prefetch" href="/assets/js/17.ecc7be70.js"><link rel="prefetch" href="/assets/js/170.c29ec18f.js"><link rel="prefetch" href="/assets/js/171.38820827.js"><link rel="prefetch" href="/assets/js/172.bbc8ffc6.js"><link rel="prefetch" href="/assets/js/173.470e21e7.js"><link rel="prefetch" href="/assets/js/174.3c2df318.js"><link rel="prefetch" href="/assets/js/175.d2690cdb.js"><link rel="prefetch" href="/assets/js/176.9ca64696.js"><link rel="prefetch" href="/assets/js/177.76f3271d.js"><link rel="prefetch" href="/assets/js/178.d7d9def2.js"><link rel="prefetch" href="/assets/js/179.b5644743.js"><link rel="prefetch" href="/assets/js/18.31fe7ecd.js"><link rel="prefetch" href="/assets/js/180.7592d5ef.js"><link rel="prefetch" href="/assets/js/181.5cb77d35.js"><link rel="prefetch" href="/assets/js/182.6fa5633c.js"><link rel="prefetch" href="/assets/js/183.b3a53d1b.js"><link rel="prefetch" href="/assets/js/184.3815c537.js"><link rel="prefetch" href="/assets/js/185.bcf4ab71.js"><link rel="prefetch" href="/assets/js/186.1cc02f6d.js"><link rel="prefetch" href="/assets/js/187.8b425fb7.js"><link rel="prefetch" href="/assets/js/188.44ccbd02.js"><link rel="prefetch" href="/assets/js/189.353b35e3.js"><link rel="prefetch" href="/assets/js/19.520992d5.js"><link rel="prefetch" href="/assets/js/190.c284595f.js"><link rel="prefetch" href="/assets/js/191.788ecc2d.js"><link rel="prefetch" href="/assets/js/192.712a164e.js"><link rel="prefetch" href="/assets/js/193.da58aba3.js"><link rel="prefetch" href="/assets/js/194.6b1b1f4d.js"><link rel="prefetch" href="/assets/js/195.c31d5c39.js"><link rel="prefetch" href="/assets/js/196.f6670c4d.js"><link rel="prefetch" href="/assets/js/197.5a1f50ab.js"><link rel="prefetch" href="/assets/js/2.ab565158.js"><link rel="prefetch" href="/assets/js/20.69e29cdc.js"><link rel="prefetch" href="/assets/js/21.2fd424ad.js"><link rel="prefetch" href="/assets/js/22.d4c0be54.js"><link rel="prefetch" href="/assets/js/23.4bb90ecc.js"><link rel="prefetch" href="/assets/js/24.c01be6b2.js"><link rel="prefetch" href="/assets/js/25.c8833687.js"><link rel="prefetch" href="/assets/js/26.8042b555.js"><link rel="prefetch" href="/assets/js/27.0d5fa4c0.js"><link rel="prefetch" href="/assets/js/28.f9735b8b.js"><link rel="prefetch" href="/assets/js/29.3af53626.js"><link rel="prefetch" href="/assets/js/30.5f1b56d1.js"><link rel="prefetch" href="/assets/js/31.544b2649.js"><link rel="prefetch" href="/assets/js/32.aa321988.js"><link rel="prefetch" href="/assets/js/33.6aba2c86.js"><link rel="prefetch" href="/assets/js/34.e1bbff24.js"><link rel="prefetch" href="/assets/js/35.233f76e0.js"><link rel="prefetch" href="/assets/js/36.cb773972.js"><link rel="prefetch" href="/assets/js/37.393d9c59.js"><link rel="prefetch" href="/assets/js/38.e2d530c5.js"><link rel="prefetch" href="/assets/js/39.acaf1cc0.js"><link rel="prefetch" href="/assets/js/40.358f731e.js"><link rel="prefetch" href="/assets/js/41.ded24b7e.js"><link rel="prefetch" href="/assets/js/42.b9f683c3.js"><link rel="prefetch" href="/assets/js/43.c8fb3e66.js"><link rel="prefetch" href="/assets/js/44.633142da.js"><link rel="prefetch" href="/assets/js/45.6095e772.js"><link rel="prefetch" href="/assets/js/46.421d8c7a.js"><link rel="prefetch" href="/assets/js/47.da50fe47.js"><link rel="prefetch" href="/assets/js/48.15ff5726.js"><link rel="prefetch" href="/assets/js/49.b662e624.js"><link rel="prefetch" href="/assets/js/5.c1b8a209.js"><link rel="prefetch" href="/assets/js/50.a8bc75df.js"><link rel="prefetch" href="/assets/js/51.51e36ae7.js"><link rel="prefetch" href="/assets/js/52.54cc6e51.js"><link rel="prefetch" href="/assets/js/53.4173561d.js"><link rel="prefetch" href="/assets/js/54.7cab8416.js"><link rel="prefetch" href="/assets/js/55.3d7317d3.js"><link rel="prefetch" href="/assets/js/56.3c22255b.js"><link rel="prefetch" href="/assets/js/57.18e46e30.js"><link rel="prefetch" href="/assets/js/58.aad57f31.js"><link rel="prefetch" href="/assets/js/59.7897f6a7.js"><link rel="prefetch" href="/assets/js/6.3131f88a.js"><link rel="prefetch" href="/assets/js/60.5cd0051a.js"><link rel="prefetch" href="/assets/js/61.d9606403.js"><link rel="prefetch" href="/assets/js/62.aede9df0.js"><link rel="prefetch" href="/assets/js/63.2c30e554.js"><link rel="prefetch" href="/assets/js/64.18228ab7.js"><link rel="prefetch" href="/assets/js/65.27cb3fba.js"><link rel="prefetch" href="/assets/js/66.2fa6c2dc.js"><link rel="prefetch" href="/assets/js/67.d274a8df.js"><link rel="prefetch" href="/assets/js/68.3069cfcf.js"><link rel="prefetch" href="/assets/js/69.4c28600f.js"><link rel="prefetch" href="/assets/js/7.89e6165d.js"><link rel="prefetch" href="/assets/js/70.4175440c.js"><link rel="prefetch" href="/assets/js/71.2ee6b435.js"><link rel="prefetch" href="/assets/js/72.c75e3bb8.js"><link rel="prefetch" href="/assets/js/73.6f8b8211.js"><link rel="prefetch" href="/assets/js/74.6c7720cf.js"><link rel="prefetch" href="/assets/js/75.cccfb229.js"><link rel="prefetch" href="/assets/js/76.f022e5da.js"><link rel="prefetch" href="/assets/js/77.dab46206.js"><link rel="prefetch" href="/assets/js/78.ca574b2a.js"><link rel="prefetch" href="/assets/js/79.3d75e618.js"><link rel="prefetch" href="/assets/js/80.091749b1.js"><link rel="prefetch" href="/assets/js/81.14db0e21.js"><link rel="prefetch" href="/assets/js/82.8a2b1809.js"><link rel="prefetch" href="/assets/js/83.84a4b599.js"><link rel="prefetch" href="/assets/js/84.11d7c222.js"><link rel="prefetch" href="/assets/js/85.273d4388.js"><link rel="prefetch" href="/assets/js/86.fb40e20c.js"><link rel="prefetch" href="/assets/js/87.3316639e.js"><link rel="prefetch" href="/assets/js/88.dfc52200.js"><link rel="prefetch" href="/assets/js/89.8d615f6e.js"><link rel="prefetch" href="/assets/js/90.1d9f08bb.js"><link rel="prefetch" href="/assets/js/91.566813e7.js"><link rel="prefetch" href="/assets/js/92.d13c6f41.js"><link rel="prefetch" href="/assets/js/93.845c42a0.js"><link rel="prefetch" href="/assets/js/94.20a37b77.js"><link rel="prefetch" href="/assets/js/95.1a498005.js"><link rel="prefetch" href="/assets/js/96.39fa7f4b.js"><link rel="prefetch" href="/assets/js/97.50f7170e.js"><link rel="prefetch" href="/assets/js/98.dd2e15d6.js"><link rel="prefetch" href="/assets/js/99.ef7ea06a.js"><link rel="prefetch" href="/assets/js/vendors~docsearch.ae6b1de9.js">
    <link rel="stylesheet" href="/assets/css/0.styles.952d6952.css">
  </head>
  <body class="theme-mode-light">
    <div id="app" data-server-rendered="true"><div class="theme-container sidebar-open have-rightmenu"><header class="navbar blur"><div title="目录" class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><img src="https://cdn.jsdelivr.net/gh/cmty256/imgs-blog@main/logo/白云.38zbldnhh180.jpg" alt="ShuangChenYue" class="logo"> <span class="site-name can-hide">ShuangChenYue</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/" class="nav-link">首页</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="CPP语言" class="dropdown-title"><!----> <span class="title" style="display:;">CPP语言</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/c5bdd8/" class="nav-link">Cpp之旅</a></li><li class="dropdown-item"><!----> <a href="/pages/279e62/" class="nav-link">Cpp专栏</a></li><li class="dropdown-item"><!----> <a href="/pages/801755/" class="nav-link">Effective_CPP</a></li><li class="dropdown-item"><!----> <a href="/pages/6b2468/" class="nav-link">muduo网络库</a></li><li class="dropdown-item"><!----> <a href="/pages/5f8c9f/" class="nav-link">Unix环境高级编程</a></li><li class="dropdown-item"><!----> <a href="/pages/3f1d21/" class="nav-link">Cpp提高编程</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="计算机基础" class="dropdown-title"><!----> <span class="title" style="display:;">计算机基础</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/7b1cb2/" class="nav-link">计算机网络</a></li><li class="dropdown-item"><!----> <a href="/pages/6048a8/" class="nav-link">操作系统</a></li><li class="dropdown-item"><!----> <a href="/pages/3b34ba/" class="nav-link">数据结构</a></li><li class="dropdown-item"><!----> <a href="/pages/412fe7/" class="nav-link">Linux</a></li><li class="dropdown-item"><!----> <a href="/pages/2dcfa1/" class="nav-link">算法</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="数据库" class="dropdown-title"><!----> <span class="title" style="display:;">数据库</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/efa3f2/" class="nav-link">基础篇</a></li><li class="dropdown-item"><!----> <a href="/pages/ccc445/" class="nav-link">MySql</a></li><li class="dropdown-item"><!----> <a href="/pages/54616e/" class="nav-link">Redis</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="嵌入式软件开发" class="dropdown-title"><!----> <span class="title" style="display:;">嵌入式软件开发</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/d142c2/" class="nav-link">电子嵌入式通信协议</a></li><li class="dropdown-item"><!----> <a href="/pages/4c6bf3/" class="nav-link">深入浅出SSD</a></li><li class="dropdown-item"><!----> <a href="/pages/d3f36a/" class="nav-link">文件系统</a></li><li class="dropdown-item"><!----> <a href="/pages/e0cca7/" class="nav-link">汇编语言</a></li><li class="dropdown-item"><!----> <a href="/pages/fab2d7/" class="nav-link">STM32</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="开发日常" class="dropdown-title"><!----> <span class="title" style="display:;">开发日常</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/e472d1/" class="nav-link">随笔（持续更新）</a></li><li class="dropdown-item"><!----> <a href="/pages/71f6ae/" class="nav-link">Git知识总结</a></li><li class="dropdown-item"><!----> <a href="/pages/db6fb8/" class="nav-link">Git备忘清单</a></li><li class="dropdown-item"><!----> <a href="/pages/e1081f/" class="nav-link">Git 创建删除远程分支</a></li><li class="dropdown-item"><!----> <a href="/pages/777b8a/" class="nav-link">nvm使用小结</a></li><li class="dropdown-item"><!----> <a href="/pages/ee770e/" class="nav-link">虚拟机固定 IP 地址</a></li><li class="dropdown-item"><!----> <a href="/pages/1ab9a6/" class="nav-link">Shell 脚本学习笔记</a></li><li class="dropdown-item"><!----> <a href="/pages/411aa4/" class="nav-link">VScode 插件 CodeGeeX 使用教程</a></li><li class="dropdown-item"><!----> <a href="/pages/0d525d/" class="nav-link">KylinV10 将项目上传至 Github教程</a></li><li class="dropdown-item"><!----> <a href="/pages/907786/" class="nav-link">KylinV10 安装 MySQL 教程（可防踩雷）</a></li><li class="dropdown-item"><!----> <a href="/pages/a2d21e/" class="nav-link">kylinV10-SP1 安装 QT</a></li><li class="dropdown-item"><!----> <a href="/pages/b561cf/" class="nav-link">高并发内存池</a></li><li class="dropdown-item"><!----> <a href="/pages/6ab6d1/" class="nav-link">USBGUARD 项目编译环境配置</a></li><li class="dropdown-item"><!----> <a href="/pages/883f02/" class="nav-link">Power_Destory 项目</a></li><li class="dropdown-item"><!----> <a href="/pages/479472/" class="nav-link">U 盘清除工具编译教程</a></li><li class="dropdown-item"><!----> <a href="/pages/9c4241/" class="nav-link">个人博客代码推送教程</a></li><li class="dropdown-item"><!----> <a href="/pages/3ad765/" class="nav-link">SVN Trunk Branches的Merge操作</a></li><li class="dropdown-item"><!----> <a href="/pages/0c0ca8/" class="nav-link">如何高效阅读嵌入式项目代码</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="前端学习" class="dropdown-title"><!----> <span class="title" style="display:;">前端学习</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/99897f/" class="nav-link">HTML与CSS</a></li><li class="dropdown-item"><!----> <a href="/pages/51542d/" class="nav-link">JS学习</a></li><li class="dropdown-item"><!----> <a href="/pages/803f9d/" class="nav-link">Vue3入门</a></li><li class="dropdown-item"><!----> <a href="/pages/ca4cfb/" class="nav-link">Vue3进阶</a></li><li class="dropdown-item"><!----> <a href="/pages/50e8d3/" class="nav-link">黑马Vue3</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="MFC" class="dropdown-title"><!----> <span class="title" style="display:;">MFC</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/a4b108/" class="nav-link">MFC编程随记</a></li><li class="dropdown-item"><!----> <a href="/pages/41acbd/" class="nav-link">MFC实现ini配置文件的读取</a></li><li class="dropdown-item"><!----> <a href="/pages/951a7a/" class="nav-link">MFC实现点击列表头排序</a></li><li class="dropdown-item"><!----> <a href="/pages/a8598f/" class="nav-link">贴图法美化Button按钮</a></li><li class="dropdown-item"><!----> <a href="/pages/054516/" class="nav-link">MFC使用细节</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="闪存" class="dropdown-title"><!----> <span class="title" style="display:;">闪存</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/b925b8/" class="nav-link">如何高效阅读嵌入式项目代码</a></li><li class="dropdown-item"><!----> <a href="/pages/28ec23/" class="nav-link">NAND Flash</a></li><li class="dropdown-item"><!----> <a href="/pages/62bf40/" class="nav-link">ARM 处理器</a></li><li class="dropdown-item"><!----> <a href="/pages/1a9374/" class="nav-link">嵌入式基础知识-存储器</a></li><li class="dropdown-item"><!----> <a href="/pages/aac5e3/" class="nav-link">闪存存储和制造技术概述</a></li><li class="dropdown-item"><!----> <a href="/pages/8f6056/" class="nav-link">芯片IO驱动力</a></li><li class="dropdown-item"><!----> <a href="/pages/d146b8/" class="nav-link">主流先进封装技术介绍</a></li><li class="dropdown-item"><!----> <a href="/pages/16f0ba/" class="nav-link">NAND Flash基础</a></li><li class="dropdown-item"><!----> <a href="/pages/90d8d0/" class="nav-link">基于PA算法的FTL引导</a></li><li class="dropdown-item"><!----> <a href="/pages/eb672b/" class="nav-link">SD逻辑擦除和物理擦除</a></li><li class="dropdown-item"><!----> <a href="/pages/747121/" class="nav-link">NAND Flash的SDR、ONFI、DDR接口</a></li><li class="dropdown-item"><!----> <a href="/pages/1eb351/" class="nav-link">【详解】Nand Flash必看知识</a></li><li class="dropdown-item"><!----> <a href="/pages/d2512a/" class="nav-link">【两万字详解】Nand Flash必看知识</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="面经" class="dropdown-title"><!----> <span class="title" style="display:;">面经</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/d69946/" class="nav-link">虎牙C++技术面经</a></li><li class="dropdown-item"><!----> <a href="/pages/29251d/" class="nav-link">金山一面复习</a></li><li class="dropdown-item"><!----> <a href="/pages/c7c01f/" class="nav-link">完美世界秋招 C++ 游戏开发面经(Cpp部分)</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="其它" class="dropdown-title"><!----> <span class="title" style="display:;">其它</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/fa256e/" class="nav-link">博客搭建</a></li><li class="dropdown-item"><!----> <a href="/pages/335531/" class="nav-link">网站收藏箱</a></li></ul></div></div> <!----></nav></div></header> <div class="sidebar-mask"></div> <div class="sidebar-hover-trigger"></div> <aside class="sidebar" style="display:none;"><!----> <nav class="nav-links"><div class="nav-item"><a href="/" class="nav-link">首页</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="CPP语言" class="dropdown-title"><!----> <span class="title" style="display:;">CPP语言</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/c5bdd8/" class="nav-link">Cpp之旅</a></li><li class="dropdown-item"><!----> <a href="/pages/279e62/" class="nav-link">Cpp专栏</a></li><li class="dropdown-item"><!----> <a href="/pages/801755/" class="nav-link">Effective_CPP</a></li><li class="dropdown-item"><!----> <a href="/pages/6b2468/" class="nav-link">muduo网络库</a></li><li class="dropdown-item"><!----> <a href="/pages/5f8c9f/" class="nav-link">Unix环境高级编程</a></li><li class="dropdown-item"><!----> <a href="/pages/3f1d21/" class="nav-link">Cpp提高编程</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="计算机基础" class="dropdown-title"><!----> <span class="title" style="display:;">计算机基础</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/7b1cb2/" class="nav-link">计算机网络</a></li><li class="dropdown-item"><!----> <a href="/pages/6048a8/" class="nav-link">操作系统</a></li><li class="dropdown-item"><!----> <a href="/pages/3b34ba/" class="nav-link">数据结构</a></li><li class="dropdown-item"><!----> <a href="/pages/412fe7/" class="nav-link">Linux</a></li><li class="dropdown-item"><!----> <a href="/pages/2dcfa1/" class="nav-link">算法</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="数据库" class="dropdown-title"><!----> <span class="title" style="display:;">数据库</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/efa3f2/" class="nav-link">基础篇</a></li><li class="dropdown-item"><!----> <a href="/pages/ccc445/" class="nav-link">MySql</a></li><li class="dropdown-item"><!----> <a href="/pages/54616e/" class="nav-link">Redis</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="嵌入式软件开发" class="dropdown-title"><!----> <span class="title" style="display:;">嵌入式软件开发</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/d142c2/" class="nav-link">电子嵌入式通信协议</a></li><li class="dropdown-item"><!----> <a href="/pages/4c6bf3/" class="nav-link">深入浅出SSD</a></li><li class="dropdown-item"><!----> <a href="/pages/d3f36a/" class="nav-link">文件系统</a></li><li class="dropdown-item"><!----> <a href="/pages/e0cca7/" class="nav-link">汇编语言</a></li><li class="dropdown-item"><!----> <a href="/pages/fab2d7/" class="nav-link">STM32</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="开发日常" class="dropdown-title"><!----> <span class="title" style="display:;">开发日常</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/e472d1/" class="nav-link">随笔（持续更新）</a></li><li class="dropdown-item"><!----> <a href="/pages/71f6ae/" class="nav-link">Git知识总结</a></li><li class="dropdown-item"><!----> <a href="/pages/db6fb8/" class="nav-link">Git备忘清单</a></li><li class="dropdown-item"><!----> <a href="/pages/e1081f/" class="nav-link">Git 创建删除远程分支</a></li><li class="dropdown-item"><!----> <a href="/pages/777b8a/" class="nav-link">nvm使用小结</a></li><li class="dropdown-item"><!----> <a href="/pages/ee770e/" class="nav-link">虚拟机固定 IP 地址</a></li><li class="dropdown-item"><!----> <a href="/pages/1ab9a6/" class="nav-link">Shell 脚本学习笔记</a></li><li class="dropdown-item"><!----> <a href="/pages/411aa4/" class="nav-link">VScode 插件 CodeGeeX 使用教程</a></li><li class="dropdown-item"><!----> <a href="/pages/0d525d/" class="nav-link">KylinV10 将项目上传至 Github教程</a></li><li class="dropdown-item"><!----> <a href="/pages/907786/" class="nav-link">KylinV10 安装 MySQL 教程（可防踩雷）</a></li><li class="dropdown-item"><!----> <a href="/pages/a2d21e/" class="nav-link">kylinV10-SP1 安装 QT</a></li><li class="dropdown-item"><!----> <a href="/pages/b561cf/" class="nav-link">高并发内存池</a></li><li class="dropdown-item"><!----> <a href="/pages/6ab6d1/" class="nav-link">USBGUARD 项目编译环境配置</a></li><li class="dropdown-item"><!----> <a href="/pages/883f02/" class="nav-link">Power_Destory 项目</a></li><li class="dropdown-item"><!----> <a href="/pages/479472/" class="nav-link">U 盘清除工具编译教程</a></li><li class="dropdown-item"><!----> <a href="/pages/9c4241/" class="nav-link">个人博客代码推送教程</a></li><li class="dropdown-item"><!----> <a href="/pages/3ad765/" class="nav-link">SVN Trunk Branches的Merge操作</a></li><li class="dropdown-item"><!----> <a href="/pages/0c0ca8/" class="nav-link">如何高效阅读嵌入式项目代码</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="前端学习" class="dropdown-title"><!----> <span class="title" style="display:;">前端学习</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/99897f/" class="nav-link">HTML与CSS</a></li><li class="dropdown-item"><!----> <a href="/pages/51542d/" class="nav-link">JS学习</a></li><li class="dropdown-item"><!----> <a href="/pages/803f9d/" class="nav-link">Vue3入门</a></li><li class="dropdown-item"><!----> <a href="/pages/ca4cfb/" class="nav-link">Vue3进阶</a></li><li class="dropdown-item"><!----> <a href="/pages/50e8d3/" class="nav-link">黑马Vue3</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="MFC" class="dropdown-title"><!----> <span class="title" style="display:;">MFC</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/a4b108/" class="nav-link">MFC编程随记</a></li><li class="dropdown-item"><!----> <a href="/pages/41acbd/" class="nav-link">MFC实现ini配置文件的读取</a></li><li class="dropdown-item"><!----> <a href="/pages/951a7a/" class="nav-link">MFC实现点击列表头排序</a></li><li class="dropdown-item"><!----> <a href="/pages/a8598f/" class="nav-link">贴图法美化Button按钮</a></li><li class="dropdown-item"><!----> <a href="/pages/054516/" class="nav-link">MFC使用细节</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="闪存" class="dropdown-title"><!----> <span class="title" style="display:;">闪存</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/b925b8/" class="nav-link">如何高效阅读嵌入式项目代码</a></li><li class="dropdown-item"><!----> <a href="/pages/28ec23/" class="nav-link">NAND Flash</a></li><li class="dropdown-item"><!----> <a href="/pages/62bf40/" class="nav-link">ARM 处理器</a></li><li class="dropdown-item"><!----> <a href="/pages/1a9374/" class="nav-link">嵌入式基础知识-存储器</a></li><li class="dropdown-item"><!----> <a href="/pages/aac5e3/" class="nav-link">闪存存储和制造技术概述</a></li><li class="dropdown-item"><!----> <a href="/pages/8f6056/" class="nav-link">芯片IO驱动力</a></li><li class="dropdown-item"><!----> <a href="/pages/d146b8/" class="nav-link">主流先进封装技术介绍</a></li><li class="dropdown-item"><!----> <a href="/pages/16f0ba/" class="nav-link">NAND Flash基础</a></li><li class="dropdown-item"><!----> <a href="/pages/90d8d0/" class="nav-link">基于PA算法的FTL引导</a></li><li class="dropdown-item"><!----> <a href="/pages/eb672b/" class="nav-link">SD逻辑擦除和物理擦除</a></li><li class="dropdown-item"><!----> <a href="/pages/747121/" class="nav-link">NAND Flash的SDR、ONFI、DDR接口</a></li><li class="dropdown-item"><!----> <a href="/pages/1eb351/" class="nav-link">【详解】Nand Flash必看知识</a></li><li class="dropdown-item"><!----> <a href="/pages/d2512a/" class="nav-link">【两万字详解】Nand Flash必看知识</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="面经" class="dropdown-title"><!----> <span class="title" style="display:;">面经</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/d69946/" class="nav-link">虎牙C++技术面经</a></li><li class="dropdown-item"><!----> <a href="/pages/29251d/" class="nav-link">金山一面复习</a></li><li class="dropdown-item"><!----> <a href="/pages/c7c01f/" class="nav-link">完美世界秋招 C++ 游戏开发面经(Cpp部分)</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="其它" class="dropdown-title"><!----> <span class="title" style="display:;">其它</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/pages/fa256e/" class="nav-link">博客搭建</a></li><li class="dropdown-item"><!----> <a href="/pages/335531/" class="nav-link">网站收藏箱</a></li></ul></div></div> <!----></nav>  <ul class="sidebar-links"><li><section class="sidebar-group collapsable depth-0"><p class="sidebar-heading"><span>电子嵌入式通信协议</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0"><p class="sidebar-heading open"><span>深入浅出SSD</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/pages/4c6bf3/" class="sidebar-link">第1章 SSD综述</a></li><li><a href="/pages/5f66ac/" class="sidebar-link">第2章 SSD主控和全闪存阵列</a></li><li><a href="/pages/4f49cc/" class="sidebar-link">第3章 SSD存储介质：闪存</a></li><li><a href="/pages/9a554c/" class="sidebar-link">第4章 SSD 主控</a></li><li><a href="/pages/e3fd38/" aria-current="page" class="active sidebar-link">第6章 FTL 综述</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_6-1-ftl-综述" class="sidebar-link">6.1 FTL 综述</a></li><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_6-2-映射管理" class="sidebar-link">6.2 映射管理</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_6-2-1-映射种类" class="sidebar-link">6.2.1 映射种类</a></li><li class="sidebar-sub-header level4"><a href="/pages/e3fd38/#_6-2-1-1-块映射" class="sidebar-link">6.2.1.1 块映射</a></li><li class="sidebar-sub-header level4"><a href="/pages/e3fd38/#_6-2-1-2-页映射" class="sidebar-link">6.2.1.2 页映射</a></li><li class="sidebar-sub-header level4"><a href="/pages/e3fd38/#_6-2-1-3-混合映射" class="sidebar-link">6.2.1.3 混合映射</a></li><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_6-2-2-映射基本原理" class="sidebar-link">6.2.2 映射基本原理</a></li><li class="sidebar-sub-header level4"><a href="/pages/e3fd38/#_6-2-2-1-计算映射表大小" class="sidebar-link">6.2.2.1 计算映射表大小</a></li><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_6-2-3-hmb" class="sidebar-link">6.2.3 HMB</a></li><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_6-2-4-映射表刷新" class="sidebar-link">6.2.4 映射表刷新</a></li></ul></li><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_6-3-垃圾回收" class="sidebar-link">6.3 垃圾回收</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_6-3-1-垃圾回收原理" class="sidebar-link">6.3.1 垃圾回收原理</a></li><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_6-3-2-写放大" class="sidebar-link">6.3.2 写放大</a></li></ul></li><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_6-4-trim" class="sidebar-link">6.4 Trim</a></li><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_6-5-磨损平衡" class="sidebar-link">6.5 磨损平衡</a></li><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_6-6-掉电恢复" class="sidebar-link">6.6 掉电恢复</a></li><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_6-7-坏块管理" class="sidebar-link">6.7 坏块管理</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_4-7-1-坏块来源" class="sidebar-link">4.7.1 坏块来源</a></li><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_4-7-2-坏块鉴别" class="sidebar-link">4.7.2 坏块鉴别</a></li><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_4-7-3-坏块管理策略" class="sidebar-link">4.7.3 坏块管理策略</a></li></ul></li><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_4-8-slc-cache" class="sidebar-link">4.8 SLC cache</a></li><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_4-9-rd-dr" class="sidebar-link">4.9 RD&amp; DR</a></li><li class="sidebar-sub-header level2"><a href="/pages/e3fd38/#_4-10-host-based-ftl" class="sidebar-link">4.10 Host Based FTL</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_4-10-1-device-based-ftl-的不足" class="sidebar-link">4.10.1 Device Based FTL 的不足</a></li><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_4-10-2-host-based-ftl-架构" class="sidebar-link">4.10.2 Host Based FTL 架构</a></li><li class="sidebar-sub-header level3"><a href="/pages/e3fd38/#_4-10-3-百度的软件定义闪存-sdf" class="sidebar-link">4.10.3 百度的软件定义闪存 SDF</a></li></ul></li></ul></li><li><a href="/pages/b2dae7/" class="sidebar-link">第9章 ECC 原理</a></li></ul></section></li><li><section class="sidebar-group collapsable depth-0"><p class="sidebar-heading"><span>文件系统</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0"><p class="sidebar-heading"><span>汇编语言</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0"><p class="sidebar-heading"><span>STM32</span> <span class="arrow right"></span></p> <!----></section></li></ul> </aside> <div><main class="page"><div class="theme-vdoing-wrapper "><div class="articleInfo-wrap" data-v-06225672><div class="articleInfo" data-v-06225672><ul class="breadcrumbs" data-v-06225672><li data-v-06225672><a href="/" title="首页" class="iconfont icon-home router-link-active" data-v-06225672></a></li> <li data-v-06225672><span data-v-06225672>嵌入式软件开发</span></li><li data-v-06225672><span data-v-06225672>深入浅出SSD</span></li></ul> <div class="info" data-v-06225672><div title="作者" class="author iconfont icon-touxiang" data-v-06225672><a href="javascript:;" data-v-06225672>霜晨月</a></div> <div title="创建时间" class="date iconfont icon-riqi" data-v-06225672><a href="javascript:;" data-v-06225672>2025-07-09</a></div> <!----></div></div></div> <!----> <div class="content-wrapper"><div class="right-menu-wrapper"><div class="right-menu-margin"><div class="right-menu-title">目录</div> <div class="right-menu-content"></div></div></div> <h1><img src="">第6章 FTL 综述<!----></h1> <!----> <div class="theme-vdoing-content content__default"><h1 id="第6章-ssd-核心技术-ftl"><a href="#第6章-ssd-核心技术-ftl" class="header-anchor">#</a> 第6章 SSD 核心技术：FTL</h1> <h2 id="_6-1-ftl-综述"><a href="#_6-1-ftl-综述" class="header-anchor">#</a> 6.1 FTL 综述</h2> <p>FTL （Flash Translation Layer，闪存转换层）用于完成主机逻辑地址空间到闪存物理地址空间的翻译（Translation），或者说映射（Mapping）。</p> <p>SSD 将用户逻辑数据写入闪存地址空间，记录该逻辑地址到物理地址的映射关系。当主机读取该数据时，SSD 便根据映射，从闪存读取数据返回给用户。完成逻辑地址空间到物理地址空间的映射，这是 FTL 最原始且基本的功能。</p> <p>FTL 除了完成基本的地址映射，还需要处理垃圾回收（GC）、磨损平衡（WearLeveling）、坏块管理、读干扰（ReadDisturb）处理、数据保持（DataRetention）处理等事情。</p> <blockquote><p>SSD 的存储介质除了 Flash，还有 RAM、3D XPoint 等新型存储介质。如无特别说明，后文说的 SSD 存储介质都是指 NAND Flash，翻译为 NAND 闪存，简称闪存。</p></blockquote> <p><strong>闪存的一些重要特性：</strong></p> <ol><li>闪存块（Block）需先擦除才能写入，不能覆盖写（out-of0place update）。</li> <li>闪存块都是有一定寿命的，可以用 PE（Program/EraseCount，编程/擦除次数）衡量。</li> <li>存在读干扰（Read Disturb）问题。</li> <li>存在数据保持（Data Retention）问题。</li> <li>闪存天生就有坏块。另外，随着 SSD 的使用，也会产生新的坏块。坏块的症状是擦写失败或者读失败（ECC 不能纠正数据错误）。坏块管理也是 FTL 的一大任务。</li> <li>QLC 或者 TLC 可以配成 SLC 模式来使用。</li></ol> <p><strong>FTL 分为 Host Based（基于主机）和 Device Based（基于设备）。</strong></p> <ul><li>Host Based 表示的是，FTL 是在 Host（主机）端实现的，用的是你自己计算机的 CPU 和内存资源，如图4-1所示。</li></ul> <p><img alt="图4-1" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.7w6nvoeht7.webp" loading="lazy" class="lazy"></p> <ul><li>Device Based 表示的是，FTL 是在 Device（设备）端实现的，用的是 SSD 上的控制器和 RAM 资源，如 图4-2 所示。</li></ul> <p><img alt="图4-2" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.60u3326i7r.webp" loading="lazy" class="lazy"></p> <h2 id="_6-2-映射管理"><a href="#_6-2-映射管理" class="header-anchor">#</a> 6.2 映射管理</h2> <h3 id="_6-2-1-映射种类"><a href="#_6-2-1-映射种类" class="header-anchor">#</a> 6.2.1 映射种类</h3> <p>根据映射粒度的不同，FTL 映射有基于块的映射，基于页的映射，还有混合映射（Hybrid Mapping）。</p> <h4 id="_6-2-1-1-块映射"><a href="#_6-2-1-1-块映射" class="header-anchor">#</a> 6.2.1.1 块映射</h4> <p>块映射，以闪存的块为映射粒度，一个用户逻辑块可以映射到任意一个闪存物理块，映射前后，每个页在块中的偏移保持不变。</p> <ul><li>缺点：存储映射表所需空间小，但其性能差，尤其是小尺寸数据的写入性能，用户即使只写入一个逻辑页，也需要把整个物理块数据先读出来，然后改变那个逻辑页的数据，最后再整个块写入。</li> <li>优点：块映射有好的连续大尺寸的读写性能，但小尺寸数据的写性能是非常糟糕的。</li></ul> <p>图4-3中，用户空间被划分成一个个逻辑区域（Region），每个区域和闪存块大小相同。</p> <p><img alt="图4-3" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.3nrgm7v2jw.webp" loading="lazy" class="lazy"></p> <blockquote><p>U 盘一般都是采用块映射（U盘使用的存储介质也是闪存，因此也是有 FTL 的），适合大数据的传输，不适合小尺寸数据的写入。所以请不要抱怨 U 盘随机性能，装系统还是选择 SSD 吧。</p></blockquote> <h4 id="_6-2-1-2-页映射"><a href="#_6-2-1-2-页映射" class="header-anchor">#</a> 6.2.1.2 页映射</h4> <p>页映射，以闪存的页为映射粒度，一个逻辑页可以映射到任意一个物理页中，每一个页都有一个对应的映射关系，如 图4-4 所示。</p> <ul><li>缺点：由于闪存页远比闪存块多，因此需要更多的空间来存储映射表。</li> <li>优点：性能更好，尤其体现在随机写上面。为追求性能，SSD 一般都采用页映射。</li></ul> <p>图4-4 中，用户空间被划分成一个个的逻辑区域，每个区域和闪存页大小相同。</p> <p><img alt="图4-4" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.839vrhbnhr.webp" loading="lazy" class="lazy"></p> <blockquote><p>实际中逻辑区域大小可能小于闪存页大小，一个闪存页可容纳若干个逻辑区域数据。</p></blockquote> <h4 id="_6-2-1-3-混合映射"><a href="#_6-2-1-3-混合映射" class="header-anchor">#</a> 6.2.1.3 混合映射</h4> <p>混合映射是块映射和页映射的结合，如 图4-5 所示。一个逻辑块映射到任意一个物理块，但在块中，每个页的偏移并不是固定不动的，块内采用页映射的方式，一个逻辑块中的逻辑页可以映射到对应物理块中的任意页。因此，它的映射表所需空间以及性能都是介于块映射和页映射之间的。</p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.1hs20gbue4.webp" loading="lazy" class="lazy"></p> <p>图4-5中，用户空间划分成一个一个逻辑区域，逻辑区域和闪存块大小相同。每个逻辑块对应着一个闪存块，但逻辑块内部又分成一个个逻辑页，与对应闪存块中的闪存页随意对应。下面对块映射、页映射和混合映射进行了对比，如表4-1所示。</p> <p>表4-1 不同映射之间的比较</p> <table><thead><tr><th></th> <th>块映射</th> <th>页映射</th> <th>混合映射</th></tr></thead> <tbody><tr><td>映射单元</td> <td>物理块</td> <td>物理页</td> <td>块页结合</td></tr> <tr><td>顺序写性能</td> <td>好</td> <td>好</td> <td>好</td></tr> <tr><td>顺序读性能</td> <td>好</td> <td>好</td> <td>好</td></tr> <tr><td>随机写性能</td> <td>很差</td> <td>好</td> <td>差</td></tr> <tr><td>随机读性能</td> <td>好</td> <td>好</td> <td>好</td></tr> <tr><td>映射表大小</td> <td>小</td> <td>大</td> <td>一般</td></tr></tbody></table> <blockquote><p>现在 SSD 基本都是采用这种映射方式。</p></blockquote> <h3 id="_6-2-2-映射基本原理"><a href="#_6-2-2-映射基本原理" class="header-anchor">#</a> 6.2.2 映射基本原理</h3> <p>用户通过 LBA（Logical Block Address，逻辑块地址）访问 SSD，每个 LBA 代表着一个逻辑块（大小一般为512B/4KB/8KB……），用户访问 SSD 的基本单元称为逻辑页（Logical Page）。</p> <p>在 SSD 内部，SSD 主控是以闪存页为基本单元读写闪存的，称闪存页为物理页（Physical Page）。用户每写入一个数据页，SSD 主控就会找一个物理页把用户数据写入，SSD 内部同时记录了这样一条映射（Map）。有了这样一个映射关系后，下次用户需要读某个逻辑页时，SSD 就知道从闪存的哪个位置把数据读取上来，如 图4-6 所示。</p> <p><img alt="图4-6" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.70a6gm539y.webp" loading="lazy" class="lazy"></p> <p>SSD 内部维护了一张逻辑页到物理页地址转换（Logical address To Physical address, L2P）的映射表（Map Table）。用户每写入一个逻辑页，就会产生一个新的映射关系，这个映射关系会加入（第一次写）或者更改（覆盖写）映射表。当读取某个逻辑页时，SSD 首先查找映射表中该逻辑页对应的物理页，然后再访问闪存读取相应的用户数据。</p> <blockquote><p>由于闪存页和逻辑页大小不同，一般前者大于后者，所以实际上不会是一个逻辑页对应一个物理页，而是若干个逻辑页写在一个物理页中，逻辑页其实是和子物理页一一对应的。</p></blockquote> <h4 id="_6-2-2-1-计算映射表大小"><a href="#_6-2-2-1-计算映射表大小" class="header-anchor">#</a> 6.2.2.1 计算映射表大小</h4> <p>一般来说，映射表大小为 SSD 容量大小的千分之一。准确来说，映射表大小是 SSD 容量大小的 1/1024。前提条件是：映射页大小为 4KB，物理地址用 4Byte 表示。这里假设了 SSD 内部映射粒度等于逻辑页大小，当然它们可以不一样。</p> <p>这里假设我们有一个 256GB 的 SSD，以 4KB 大小的逻辑页为例，那么用户空间一共有 64M（256GB/4KB）个逻辑页，也就意味着 SSD 需要有能容纳 64M 条映射关系的映射表。映射表中的每个单元（entry）存储的就是物理地址（PhysicalPage Address），假设其为4字节（32 bits），那么整个映射表的大小为 64M×4B = 256MB。</p> <p>对于绝大多数 SSD，我们可以看到上面都有板载 DRAM，其主要作用就是存储这张映射表，如 图4-7 所示。在 SSD 工作时，全部或绝大部分的映射表都可以放在 DRAM 上，映射关系可以快速访问。</p> <p><img alt="image" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.5tqv81l7pj.webp" loading="lazy" class="lazy"></p> <p>入门级 SSD 或者移动存储设备（比如 eMMC、UFS），出于成本和功耗考虑，它们采用 DRAM-Less 设计，即不带 DRAM，比如经典的 Sandforce 主控，它并不支持板载 DRAM，它采用二级映射（见 图4-8）。一级映射表常驻 SRAM，二级映射表小部分缓存在 SRAM，大部分都存放在闪存上。</p> <p><img alt="图4-8" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.4n7k4ouw7y.webp" loading="lazy" class="lazy"></p> <p>二级表就是 L2P（Logical address To Physical address，逻辑地址到物理地址转换）表，它被分成一块一块（Region）的，大部分存储在闪存中，小部分缓存在 RAM 中。一级表则存储这些块在闪存中的物理地址，由于它不是很大，一般都可以完全放在 RAM 中。</p> <p>SSD 工作时，对带 DRAM 的 SSD 来说，只需要查找 DRAM 当中的映射表，获取到物理地址后访问闪存便会得到用户数据，这期间只需要访问一次闪存。而对不带 DRAM 的 SSD 来说，它首先会查看该逻辑页对应的映射关系是否在 SRAM内：如果在，直接根据映射关系读取闪存；如果不在，那么它首先需要把映射关系从闪存中读取出来，然后再根据这个映射关系读取用户数据，这就意味着相比于有 DRAM 的 SSD，它需要读取两次闪存才能把用户数据读取出来，底层有效带宽减小，如图4-9所示。</p> <p><img alt="图4-9" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.escuv7l1z.webp" loading="lazy" class="lazy"></p> <h3 id="_6-2-3-hmb"><a href="#_6-2-3-hmb" class="header-anchor">#</a> 6.2.3 HMB</h3> <p>映射表除了可以放在板载 DRAM、SRAM 和闪存中，它还可以放到主机的内存中。NVME1.2（及后续版本）有个重要的功能就是 HMB（Host MemoryBuffer，主机高速缓冲存储器）：主机在内存中专门划出一部分空间给 SSD 用，SSD 可以把它当成自己的 DRAM 使用。因此，映射表完全可以放到主机端的内存中去，如图4-10所示。</p> <p><img alt="图4-10" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.1020h64z7h.webp" loading="lazy" class="lazy"></p> <p>在性能上，它应该介于带 DRAM 和不带 DRAM（映射表绝大多数存放在闪存）之间，因为 SSD 访问主机端 DRAM 的速度肯定比访问 SSD 端 DRAM 的速度要慢，但还是比访问闪存的速度（约40μs）要快。HMB功能允许主控像使用SSD上的 DRAM 一样使用主机 DRAM。具体说来，就是主机在主存中专门划出一块内存给 SSD 使用，该内存在物理上可以不连续，SSD 不仅可以用它来存放映射表，还可以用它来缓存用户数据，具体怎么用，取决于 SSD 设计者。</p> <p>如前所述，SSD 有两种设计：</p> <p>一种是带 DRAM 的，DRAM 用于缓存数据和存放映射表，目前主流 SSD 都是带 DRAM 的；带 DRAM 的 SSD 设计，其优势是性能好，映射表完全可以放在 DRAM 上，查找和更新迅速；劣势就是由于增加了一个 DRAM，提高了 SSD 的成本，也加大了 SSD 的功耗。</p> <p>还有一种就是不带 DRAM（DRAM-Less）的，缓存数据用主控上的 SRAM，映射表采用两级映射——一级映射和少量的二级映射放 SRAM，二级映射数据大多数存放在闪存上，这种 DRAM-Less 设计多为入门级 SSD 使用。DRAM-Less 的 SSD 设计则正好相反，优势是成本和功耗相对低，缺点是性能差。由于映射表绝大多数存储在闪存中，对随机读来说，每次读用户数据，需要访问两次闪存，第一次是获取映射表，然后才是真正读取用户数据。</p> <h3 id="_6-2-4-映射表刷新"><a href="#_6-2-4-映射表刷新" class="header-anchor">#</a> 6.2.4 映射表刷新</h3> <p>映射表在SSD掉电前，是需要把它写入到闪存中去的。下次上电初始化时，需要把它从闪存中部分或全部加载到SSD的缓存（DRAM或者SRAM）中。随着SSD的写入，会不断增加新的映射关系，为防止异常掉电导致这些新的映射关系丢失，SSD的固件不仅仅只在正常掉电前把这些映射关系刷新到闪存中去，而是在SSD运行过程中，按照一定策略把映射表写进闪存。这样，即使发生异常掉电，丢失的也只是一小部分映射关系，上电时可以较快地重建这些映射关系。</p> <p><strong>一般有以下几种情况触发映射表写入：</strong></p> <ul><li>新产生的映射关系累积到一定的阈值</li> <li>用户写入的数据量达到一定的阈值</li> <li>闪存写完闪存块的数量达到一定的阈值</li></ul> <p><strong>写入策略：</strong></p> <ul><li>全部更新：全部更新表示的是缓存中映射表（干净的和不干净的）全部写入到闪存</li> <li>增量更新：增量更新的意思是只把新产生的（不干净的）映射关系刷入到闪存中去</li></ul> <h2 id="_6-3-垃圾回收"><a href="#_6-3-垃圾回收" class="header-anchor">#</a> 6.3 垃圾回收</h2> <h3 id="_6-3-1-垃圾回收原理"><a href="#_6-3-1-垃圾回收原理" class="header-anchor">#</a> 6.3.1 垃圾回收原理</h3> <p>我们假设该 SSD 底层有4个通道（CH0～CH3），连接着4个Die（每个通道上的 Die 可并行操作），假设每个 Die 只有6个闪存块（Block0～Block5），所以一共24个闪存块。每个闪存块内有9个小方块，每个小方块的大小和逻辑页大小一样。24个闪存块中，我们假设其中的20个闪存块大小为 SSD 容量，就是主机端看到的 SSD 大小；另外4个闪存块是超出 SSD 容量的预留空间，我们称之为 OP，如 图4-14所示。</p> <p><img alt="图4-14" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.b8qx9xbht.webp" loading="lazy" class="lazy"></p> <p>我们顺序写入4个逻辑页，分别写到不同通道的 Die 上，这样写的目的是增加底层的并行性，提升写入性能，如图4-15所示。</p> <p><img alt="图4-15" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.4ckqbt1et0.webp" loading="lazy" class="lazy"></p> <p>我们继续顺序写入，固件则把数据交错写入到各个 Die 上，直到写满整个 SSD 空间（主机端看到的）如图4-16所示。</p> <p><img alt="图4-16" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.9kg0ysxf99.webp" loading="lazy" class="lazy"></p> <p>整个盘写满了（从用户角度来看也就是整个用户空间写满了，但在闪存空间，由于 OP（OverProvisioning，预留空间） 的存在，并没有写满）。那如果想写入更多，只能把看过的内容删除了，腾出空间放新的内容。</p> <p>下面我们继续拷入。假设还是从逻辑页1开始写入。这时，SSD 会把新写入的逻辑页写入到所谓的 OP 空间。对 SSD 来说，不存在什么用户空间和 OP 空间，它只会看到闪存空间。主机端来数据，SSD 就往闪存空间写。图4-17 中出现了深色方块，怎么回事？因为逻辑页1～4的数据已更新，写到新的地方，那么之前那个位置上的逻辑页1～4数据就失效了，过期了，变垃圾了。用户更新数据，由于闪存不能在原位置覆盖写，固件只能另找闪存空间写入新的数据，因此导致原闪存空间数据过期，形成垃圾。</p> <p><img alt="图4-17" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.231psbmi2f.webp" loading="lazy" class="lazy"></p> <p>继续顺序写入，深色方块越来越多（垃圾数据越来越多）。所有闪存空间都写满后，小 SSD 就是下面这个样子（见图4-18）。</p> <p><img alt="图4-18" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.7zq9zc3tpk.webp" loading="lazy" class="lazy"></p> <p>等所有 Die 上的 Block 5 写满后，所有 Die 上的 Block 0 也全部变色了（这些数据都是垃圾）。现在不仅整个用户空间都写满，整个闪存空间也都满了。如果用户想继续写入后续的逻辑页（36之后的），该怎么办呢？</p> <p>这时，就需要垃圾回收了。我们暂时从之前的SSD系统中走出来，看看什么是垃圾回收。</p> <blockquote><p>需要说明的是，实际中是不会等所有闪存空间都写满后才开始做GC的，而是在满之前就触发GC，这里只是为描述GC而做的假设。</p></blockquote> <p>垃圾回收，就是把某个闪存块上的有效数据（图4-19中浅色方块）读出来，重写，然后把该闪存块擦除，就得到新的可用闪存块了。</p> <p><img alt="图4-19" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.escv4z4sr.webp" loading="lazy" class="lazy"></p> <p>图4-19中，Block x上面有效数据为A、B、C, Block y上面有效数据为D、E、F、G，其余方块为无效数据。垃圾回收机制就是先找一个可用Block z，然后把Block x和Block y的有效数据搬移到Block z上面去，这样Block x和Block y上面就没有任何有效数据，可以擦除变成两个可用的闪存块，如图4-20所示。</p> <p><img alt="图4-20" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.5fkfmp8p8i.webp" loading="lazy" class="lazy"></p> <p>再回到我们的小小SSD系统中来。</p> <p>上例中，由于我们是顺序写入，垃圾集中在Block 0上，上面没有任何有效数据，我们把它们擦除就可以腾出新的写入空间，用户就可以把新的数据写入到垃圾回收完成的Block 0上了。从这个例子中我们可以看出：顺序写，即使是闪存</p> <p>空间写满后的写（Full Drive写），性能也是比较好的，因为垃圾回收可以很快完成（也许只需要一个擦除动作）。</p> <p>但现实是残酷的：用户写入数据，更多的可能是随机写入数据。下面是一个闪存空间经历随机写满后的样子（见图4-21）。</p> <p><img alt="图4-21" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.1020hfz0fb.webp" loading="lazy" class="lazy"></p> <p>用户如果继续往SSD上写入数据，那么SSD怎么处理？当然需要做垃圾回收。不过，SSD内部状况比之前看到的复杂多了，垃圾数据分散在每个闪存块上，而不是集中在某几个闪存块上。这个时候，如何挑选需要回收的闪存块呢？答案显而易见，挑垃圾比较多的闪存块来回收，因为有效数据少，要搬移的数据少，这样腾出空闪存块的速度快。</p> <p>对上面每个闪存块的垃圾数（深色方块）做个统计，如表4-2所示。</p> <p><img alt="表4-2" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.51dzvu3y6e.webp" loading="lazy" class="lazy"></p> <p>由于是同时往4个通道上写，我们需要每个通道都有一个空闲的闪存块，因此，我们做垃圾回收时，不是回收某个闪存块，而是所有通道上都要挑一个。一般选择每个Die上块号一样的所有闪存块做垃圾回收。上例中，Block 0上的垃圾数量最多（24个深色方块，最多），因此我们挑Block 0作为垃圾回收的闪存块（这里忽略PE Count等因素，只看垃圾数）。回收完毕，我们把之前Block 0上面的有效数据（浅色方块）重新写回到这些闪存块（这里，我们假设回收的有效数据和用户数据写在同一个闪存块，实际上，它们可能是分开写的），如图4-22所示。</p> <p><img alt="图4-22" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.3d4mynl8q6.webp" loading="lazy" class="lazy"></p> <p>这时，有了空闲的空间（白色方块），用户就可以继续写入数据了。</p> <blockquote><p>江湖传言：SSD 越写越慢。没错，其实这是有科学依据的：可用闪存空间富裕时，SSD 是无须做 GC 的，因为总有空闲的空间可写。SSD 使用早期，由于没有触发 GC，无须额外的读写，所以速度很快。慢慢地会发现 SSD 变慢了，主要是因为 SSD 需要做 GC。</p> <p>另外，从上面的例子来看，如果用户顺序写的话，垃圾比较集中，利于SSD做垃圾回收；如果用户是随机写的话，垃圾产生比较分散，SSD 做垃圾回收相对来说就更慢，所以性能没有前者好。因此，SSD 的 GC 性能跟用户写入数据的模式（随机写还是顺序写）也是有关的。</p></blockquote> <h3 id="_6-3-2-写放大"><a href="#_6-3-2-写放大" class="header-anchor">#</a> 6.3.2 写放大</h3> <h2 id="_6-4-trim"><a href="#_6-4-trim" class="header-anchor">#</a> 6.4 Trim</h2> <p>当用户删除掉文件 File A 时，其实它只是切断用户与操作系统的联系，即用户访问不到这些地址空间；而在 SSD 内部，逻辑页与物理页的映射关系还在，文件数据在闪存当中也是有效的，如图4-35所示。</p> <p><img alt="图4-35" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.6f0j5bw42r.webp" loading="lazy" class="lazy"></p> <p>Trim 是一个新增的 ATA 命令（Data Set Management），专为 SSD 而生。当用户删除一个文件时，操作系统（对 Windows 来说，它自 Windows 7 开始支持 Trim）会发 Trim 命令给 SSD，告诉 SSD 该文件对应的数据无效了。一旦 SSD 知道哪些数据无效之后，在做垃圾回收的时候就可以把这些删除掉的数据抛弃掉，不做无谓的数据搬移。这样不仅增强了 SSD 的性能，还延长了 SSD 寿命。</p> <blockquote><p>SCSI 里面的同等命令叫 UNMAP, NVMe 里面叫 Deallocate。它们指的都是同一个功能。</p></blockquote> <p>举个例子。主机通过 Trim 命令告诉 SSD：我 0～7 的逻辑页上的数据删除了，你可以把它们当垃圾处理。收到 Trim 命令之前，逻辑页 0～7 有以下映射，它们分别写在物理地址 PBA a～h，如图4-36所示。</p> <p><img alt="图4-36" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.9rj8zpg89i.webp" loading="lazy" class="lazy"></p> <p>一般 FTL 都有这3个表。FTL 映射表记录每个 LBA 对应的物理页位置。Valid Page Bit Map（VPBM）记录每个物理块上哪个页有有效数据，ValidPage Count（VPC）则记录每个物理块上的有效页个数。通常 GC 会使用 VPC 进行排序来回收最少有效页的闪存块；VPBM 则是为了在 GC 时只读有用的数据，也有部分 FTL 会省略这个表。</p> <p>如图4-36所示，FTL 的映射往往是非常分散的，连续的逻辑页对应地址会在很多不同的闪存块上。SSD 收到 Trim 命令后，为了实现数据删除，固件要按顺序做以下的事情（图4-37中的步骤1～4）。</p> <p><img alt="图4-36" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.5c0tug7cz8.webp" loading="lazy" class="lazy"></p> <p>图4-37中的步骤5～7是 Trim 命令处理后，GC 的处理，它们不是 Trim 命令处理的部分。Trim 命令是不会触发 GC 的。</p> <h2 id="_6-5-磨损平衡"><a href="#_6-5-磨损平衡" class="header-anchor">#</a> 6.5 磨损平衡</h2> <p>磨损平衡，就是让 SSD 中的每个闪存块的磨损（擦除）都保持均衡。</p> <p>一个闪存块寿命有多长呢？如图4-38所示。</p> <p><img alt="图4-38" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.231pxtprfn.webp" loading="lazy" class="lazy"></p> <ul><li>冷数据（Cold Data）： 所谓冷数据，就是用户不经常更新的数据，比如用户写入 SSD 的操作系统数据、只读文件数据、小电影等；</li> <li>热数据（HotData）： 热数据就是用户更新频繁的数据。数据的频繁更新，会在 SSD 内部产生很多垃圾数据（新的数据写入导致老数据失效）。</li> <li>年老的（Old）块:： 擦写次数比较多的闪存块；</li> <li>年轻的（Young）块： 擦写次数比较少的闪存块</li></ul> <p>SSD 很容易区分年老的块和年轻的块，看它们的 EC（Erase Count，擦除次数）就可以了，大的就是老的，小的就是年轻的。</p> <p>SSD 一般有动态磨损平衡（Dynamic WL）和静态磨损平衡（Static WL）两种算法。</p> <ul><li>动态磨损平衡： 动态磨损平衡算法的基本思想是把热数据写到年轻的块上，即在拿一个新的闪存块用来写的时候，挑选擦写次数小的；</li> <li>静态磨损平衡： 静态磨损平衡算法基本思想是把冷数据写到年老的块上，即把冷数据搬到擦写次数比较多的闪存块上。</li></ul> <hr> <p>冷数据由于不经常更新，它写在一个或者几个闪存块上后，基本保持不动，这样，这些闪存块的擦写次数就不会增加；相反，对别的闪存块，由于经常拿来写入用户数据，擦写次数是一直增长的。这样就导致闪存块的擦写不均衡。因此，固件需要做静态磨损平衡。</p> <p>固件具体做静态磨损平衡的时候，一般使用GC机制来做，只不过它挑选源闪存块时，不是挑选有效数据最小的闪存块，而是挑选冷数据所在的闪存块。</p> <hr> <p>静态磨损平衡可能导致冷数据和热数据混在同一个闪存块上，即冷数据可能跟用户刚写入的数据混在一起，或者冷数据和GC的数据写在一起，或者三者写在一起。</p> <p>1）SWL 数据和用户数据混在一起写在同一个闪存块上，如图4-39所示。</p> <p><img alt="图4-39" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.esd0noxys.webp" loading="lazy" class="lazy"></p> <p>2）SWL 数据和 GC 数据混在一起写在同一个闪存块上，如图4-40所示。</p> <p><img alt="图4-40" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.64dpc8l8xv.webp" loading="lazy" class="lazy"></p> <p>3）三者全都混在一起写在同一个闪存块上，如图4-41所示。</p> <p><img alt="图4-41" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.4914jmd4qb.webp" loading="lazy" class="lazy"></p> <p>解决办法如图4-42所示，做静态磨损平衡的时候，用专门的闪存块来放冷数据，即不与用户或者 GC 写入同一个闪存块。这样冷数据就单独写在某些闪存块上，它们一般不会挑选为 GC 的源闪存块，也就避免了这些冷数据的频繁搬移。它只有在下一次需要做静态磨损平衡的时候，才会从一个闪存块搬到另外一个闪存块。</p> <p><img alt="图4-42" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.54xlz2phoc.webp" loading="lazy" class="lazy"></p> <blockquote><p>不同的 SSD 有不同的静态磨损平衡做法。如果不在乎写放大（EC预算够大，不差钱），也不在乎冷数据搬移导致的性能下降，那么冷热数据混在一起就一起，毕竟实现简单（不需要另外管理静态磨损平衡的闪存块）；相反，如果对写放大比较敏感的话，那么最好还是冷热数据分开。</p></blockquote> <h2 id="_6-6-掉电恢复"><a href="#_6-6-掉电恢复" class="header-anchor">#</a> 6.6 掉电恢复</h2> <p>掉电分两种，一种是正常掉电，另一种是异常掉电。</p> <p><strong>正常掉电。</strong></p> <p>在掉电前，主机会通过命令通知 SSD，比如 SATA 中的 IdleImmediately, SSD 收到该命令后，主要会做以下事情：</p> <ul><li>把 buffer 中缓存的用户数据刷入闪存。</li> <li>把映射表刷入闪存。</li> <li>把闪存的块信息写入闪存（比如当前写的是哪个闪存块，以及写到该闪存块的哪个位置，哪些闪存块已经写过，哪些闪存块又是无效的等）。</li> <li>把 SSD 其他信息写入闪存。</li></ul> <p>主机等 SSD 处理完以上事情后，才会真正停止对 SSD 的供电。正常掉电不会导致数据的丢失</p> <p><strong>异常掉电</strong></p> <p>就是 SSD 在没有收到主机的掉电通知时就被断电；或者收到主机的掉电通知，但还没有来得及处理上面提到的那些事情，就被断电了。</p> <ul><li>异常掉电可能会导致数据的丢失，比如缓存在 SSD 中的数据来不及写到闪存，掉电导致这部分数据丢失。</li> <li>还有，根据闪存特性，如果掉电发生在写 MLC 的 Upperpage，会导致其对应的 Lower Page 数据遭到破坏，也就是意味着之前写入闪存的数据也可能由于异常掉电导致丢失。</li> <li>异常掉电恢复的目的一方面是尽可能恢复用户数据，把损失减到最低；另一方面是让 SSD 经历异常掉电后还能正常工作。</li></ul> <hr> <p>一个 SSD，除了数据掉电不丢失的闪存，还需要有掉电数据丢失的 RAM、SRAM 或者 DRAM。</p> <p>闪存的作用是存储数据，而 RAM 的作用主要是 SSD 工作时用以缓存用户数据和存放映射表（Map Table，逻辑地址映射闪存物理地址）。所以一旦掉电，RAM 的数据就会丢失。</p> <p>为防止异常掉电导致的数据丢失，一个简单的设计就是在 SSD 上加电容，SSD 一旦检测到掉电，就让电容开始放电，然后把 RAM 中的数据刷到闪存上面去，从而避免数据丢失。</p> <blockquote><p>还有一个比较前卫的想法，就是把 RAM 这种 Volatile（掉电数据丢失）的东西，用 Non-Volatile（掉电数据不丢失）的东西来替代，但要求这种 Non-Volatile 的东西性能上接近 RAM。这样，整个 SSD 都是 Non-Volatile 的了。Intel 和 Micron 合作开发的 3D XPoint，可作为一个选择。3D XPoint 兼有闪存掉电数据不丢失和内存快速访问的特点。</p></blockquote> <h2 id="_6-7-坏块管理"><a href="#_6-7-坏块管理" class="header-anchor">#</a> 6.7 坏块管理</h2> <h3 id="_4-7-1-坏块来源"><a href="#_4-7-1-坏块来源" class="header-anchor">#</a> 4.7.1 坏块来源</h3> <ul><li>出厂坏块（Factory Bad Block）：闪存从工厂出来，就或多或少的有一些坏块。</li> <li>增长坏块（Grown Bad Block）：随着闪存的使用，一些初期好块也会因为擦写磨损变成坏块。</li></ul> <h3 id="_4-7-2-坏块鉴别"><a href="#_4-7-2-坏块鉴别" class="header-anchor">#</a> 4.7.2 坏块鉴别</h3> <p>刚出厂的闪存一般都会被擦除，里面的数据全是0xFF，而坏块会被的打上不同的标记。例如：TOSHIBA某型号闪存，如图4-47 所示：</p> <p><img alt="图4-47" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.9gwff3wolq.png" loading="lazy" class="lazy"></p> <p>它会在出厂坏块的第一个闪存页和最后一个闪存页的数据区第一个字节和Spare区第一个字节写上一个非0xFF的值。</p> <p>用户使用闪存时应先按照闪存文档扫描所有闪存块剔除坏块，建立一张坏块表。TOSHIBA建议按照下面的流程来建立坏块表（见图4-48）。</p> <p><img alt="图4-48" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.6wql2h6fch.webp" loading="lazy" class="lazy"></p> <p>部分闪存厂商会把坏块信息存储在闪存内部某个地方（掉电不丢失），用户建立坏块时只需读取闪存的那个特定区域。例如：Micron，它的闪存内部有个叫 OTP（One Time Programming）的区域，出厂坏块信息可以存在里面。</p> <p>对增长坏块而言，它的出现会通过读写擦等操作反映出来。比如读到 UECC（Uncorrectable Error Correction Code，数据没有办法通过 ECC 纠错恢复）、擦除失败、写失败，这都是一个坏块出现的症状。用户应该把这些坏块加入坏块表，不再使用。</p> <h3 id="_4-7-3-坏块管理策略"><a href="#_4-7-3-坏块管理策略" class="header-anchor">#</a> 4.7.3 坏块管理策略</h3> <p>一般有两种策略管理坏块，一是略过（Skip）策略，二是替换（Replace）策略。</p> <ol><li><p><strong>略过策略</strong></p> <p>用户根据建立的坏块表，在写闪存的时候，一旦遇到坏块就跨过它，写下一个Block。</p> <p>SSD 的存储空间是闪存阵列，一般有几个并行通道，每个通道上连接了若干个闪存。以图4-49为例，该 SSD 有四个通道，每个通道上挂了一个闪存 Die。SSD 向四个 Die 依次写入。假设 Die 1上有个 Block B 是坏块，若固件采取坏块略过策略，则写完 Block A 时，接下来便会跨过 Block B 写到 Die 2 的 Block C 上面去。</p></li></ol> <p><img alt="图4-49" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.6bgxg6jh7b.webp" loading="lazy" class="lazy"></p> <ol start="2"><li><p><strong>替换机制</strong></p> <p>当某个 Die 上发现坏块时，它会被该 Die 上的某个好块替换。用户在写数据的时候，不是跨过这个 Die，而是写到替换块上面去。此策略需额外保留一部分好的闪存块，用于替换用户空间的坏块。整个 Die 上闪存块就划分为两个区域：用户空间和预留空间，如图4-50所示。</p> <p>还是以上面的情况为例：用户写入数据时，当碰到坏块B，它不会略过Die 1不写，而是写入到Block B的替换者Block B′上面去。</p></li></ol> <p><img alt="图4-50" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.3yeayza92h.webp" loading="lazy" class="lazy"></p> <p>采用替换策略，SSD 内部需维护一张重映射表（Remap Table）：坏块到替换块的映射，比如图4-51的 B→B′。当 SSD 需要访问 Block B 时，它需要查找重映射表，实际访问的物理 Block 应该是 B′。</p> <p><img alt="图4-51" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.wiexrbc69.webp" loading="lazy" class="lazy"></p> <p>两者策略优劣：</p> <ul><li><strong>略过策略：</strong> 性能不稳定，Die 的并行度在1和4给 Die 之间；</li> <li><strong>替换策略：</strong> 性能稳定，Die 的并行度总是4个 Die；有木桶效应，如果某个 Die 质量较差，则整个 SSD 可用的闪存块则受限于那个坏的 Die。</li></ul> <h2 id="_4-8-slc-cache"><a href="#_4-8-slc-cache" class="header-anchor">#</a> 4.8 SLC cache</h2> <p>SLC、MLC 和 TLC 在性能和寿命上的直观比较，如表4-10所示。</p> <p><img alt="表4-10" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.6m3r9cdcjr.webp" loading="lazy" class="lazy"></p> <p>SLC 有速度优势，因此拿它在做 CaChe 使用可让 SSD具有更好的突发性能。</p> <p>这里所说的 SLC Cache，不是说单独拿 SLC 闪存来做 Cache，而是把 MLC 或者 TLC 里面的一些闪存块配置成 SLC 模式来访问，而这个特性一般的 MLC 或者 TLC 都是支持的</p> <ol><li><p>使用 SLC Cache 的出发点，主要有以下几点：</p> <ul><li><p><strong>性能考虑：</strong> SLC 性能好，用户数据写到 SLC 比直接写到 MLC 或者 TLC 上快很多。</p></li> <li><p>**防止 Lower Page 数据被带坏： **用户数据写到 SLC，不存在写 Upper Page 或者 Extra Page 带坏 Lower Page 数据的可能。</p></li> <li><p><strong>解决闪存的缺陷：</strong> 比如有些 MLC 或者 TLC 的闪存块，如果没有写满，然后去读的话，可能会读到 ECC 错误，而对 SLC 模式下的闪存块，就没有这个问题。</p></li> <li><p><strong>更多的数据写入量：</strong> SLC 更耐写。</p></li></ul></li> <li><p>SLC Cache 写入策略有：</p> <ul><li><p><strong>强制 SLC 写入：</strong> 用户写入数据时，必须先写入到 SLC 闪存块，然后通过 GC 搬到 MLC 或者 TLC 闪存块；能保护 Lower Page 数据，但是因为一方面要把 SLC 的数据搬到 MLC 或者 TLC ，以腾出 SLC 空间供新用户数据的写入，同时又要把用户数据写入到 SLC，性能肯定比只写 MLC 或者 TLC 慢。</p></li> <li><p><strong>非强制 SLC 写入：</strong> 用户写入数据时，如果有 SLC 闪存块，则写入到 SLC 闪存块，否则直接写到 MLC 或者 TLC 闪存块。不能保护 Lower Page 数据，但有更好的后期写入性能，因为在 SLC 闪存块耗尽的情况下，用户数据直接写入到 MLC 或者 TLC。</p></li></ul></li></ol> <p>强制写入 SLC 策略， SLC 数据最后都要搬到 MLC 或者 TLC，所以还是存在直接写 MLC 或者 TLC 的事实，也就是还是存在 Lower Page 数据被带坏的可能。是的，没错，做 GC（数据搬移）是有这个问题。但是，如果我们在目标闪存块没有被写满前，不把源闪存块擦除，这样即使 Lower Page 数据被带坏，它还是能通过读源闪存块恢复数据，是不是？</p> <ol start="3"><li><p>SLC Cache 办法：</p> <ul><li><p><strong>静态 SLC Cache：</strong> 拿出一些 Block 专门用做 SLC Cache。</p></li> <li><p><strong>动态SLC Cache：</strong> 所有的 MLC 和 TLC 都有可能挑来当 SLC Cache，SLC 和 TLC 不分家；</p></li> <li><p><strong>两者混合：</strong> 即既有专门的 SLC 闪存块，还能把其他通用闪存块拿来当 SLC Cache。</p></li></ul></li></ol> <h2 id="_4-9-rd-dr"><a href="#_4-9-rd-dr" class="header-anchor">#</a> 4.9 RD&amp; DR</h2> <p>RD 指的是 Read Disturb, DR 指的是 Data Retention。两者都能导致数据丢失，但原理和固件处理方式都不一样。</p> <ol><li><p>RD</p> <p>每次读一个闪存页都会在其他字线（Wordline）上加较高的电压以保证晶体管导通。长此以往，由于电子进入浮栅极过多，从而导致比特翻转：1→0。当出错比特数超出 ECC 的纠错能力时，数据就会丢失。这就是 RD 的原理。</p> <p>如果我们能保证某个闪存块读的次数低于某个阈值，在比特发生翻转之前（或者翻转的比特低于某个值时），就对这个闪存块上的数据进行一次刷新：把闪存块上的数据搬到别的闪存块上（或者先搬到别的闪存块上，然后擦除原闪存块后，再复制回来），防患于未然，这样就能解决 RD 导致数据丢失的问题。</p> <p>因此，FTL 应该有记录每个闪存块读次数的一张表：每读一次该闪存块，对应的读次数加1。当 FW 检测到某个闪存块读的次数超过某个阈值，就刷新该闪存块。当数据写到新的闪存块后，读次数归零，一切重新开始。每个闪存块的读次数，掉电时应该保存到闪存上，重新上电时，再加载它们。</p> <p>事实上，当某个闪存块上的读次数超出阈值时，上面的数据翻转可能并没有超过很多（可设阈值），这种情况就没有必要立刻刷新。毕竟，刷新代来的读数据和写数据，需要耗时间和擦写次数，对性能和闪存寿命有影响。因此，有些 FTL 为避免“过”刷新，可能会在读次数超过阈值后，先检测比特翻转数，然后决定是否真正需要刷新，如果不需要立刻刷新，会重新设置一个更大的阈值，待下次读的次数达到新阈值后，重复之前的操作。</p> <p>其实，RD 与闪存的年龄有关：年龄越大（PE 越大），对 RD 的免疫力越低。因此，对阈值的设定，采用动态的才是合理的，即对不同的 PE，读阈值应该不同。具体来说，PE 越大，读阈值应该越小。</p> <p>关于刷新动作，有Block（阻塞）和Non-block（非阻塞）两种处理方式。所谓阻塞方式，就是固件把其他事情都放在一边，专门处理闪存块的刷新；所谓非阻塞方式，就是闪存块的刷新与其他操作同时进行（Interleave操作）。前者处理方式劣势明显，那就是带来很长的命令时延：在处理闪存块的刷新的时候，就不能执行读写操作，导致读写推后。随着闪存块尺寸的增大，这种处理方式的劣势越发凸显。所以，现在的FTL一般都采用非阻塞的刷新处理方式。</p></li> <li><p>DR</p> <p>绝缘氧化层把存储在浮栅极的电子关在里面，但是，随着时间的推移，还是有电子从里面跑出来。当跑出来的电子达到一定数量时，就会使存储单元的比特发生翻转：0→1（注意，RD 是使1翻转为0），当出错比特数超出 ECC 的纠错能力，数据就丢失。这就能解释为什么你的固态硬盘如果很长时间不用，可能就启动不了，或者启动很慢（固件需要处理由于DR引起的数据错误）的现象了。</p> <p>问题来了，为什么 SSD 长久不用数据就会丢失，而经常使用却不会呢？原因是 FW 或者 FTL 立功了。针对 DR 这个问题，稍微好一点的 SSD, FTL 都会有相应的处理。怎么处理呢？FTL 在 SSD 上电或者平时运行时，每隔一段时间对闪存空间进行扫描，当发现比特翻转超出一定阈值时，跟 RD 处理一样，进行数据刷新，这样就能避免数据彻底丢失。SSD 如果常年不上电，FTL 根本就没有机会执行这些操作，只能眼睁睁地看着电子流失。</p></li></ol> <h2 id="_4-10-host-based-ftl"><a href="#_4-10-host-based-ftl" class="header-anchor">#</a> 4.10 Host Based FTL</h2> <p>按照 FTL 放在哪里划分，SSD 有 Host Based FTL 和 Device Based FTL 两种模式。</p> <ul><li><strong>Host Based FTL：</strong> 放在主机驱动程序中，也有一些企业级 SSD 采用了 Host Based FTL，像垃圾回收、磨损平衡、坏块管理等都放在主机驱动程序中完成，这种模式的优点是可以实现差异化。</li> <li><strong>Device Based FTL：</strong> 放在 SSD 主控内部，大部分企业级 SSD 和几乎全部消费级 SSD 都是 Device Based，SSD主控芯片做了包括FTL在内的所有控制工作。</li></ul> <h3 id="_4-10-1-device-based-ftl-的不足"><a href="#_4-10-1-device-based-ftl-的不足" class="header-anchor">#</a> 4.10.1 Device Based FTL 的不足</h3> <p>图4-52所示是两种模式的架构对比。看得出来，从逻辑上来说，一个完整的 Device Based SSD 系统可以分为三块：</p> <p><img alt="图4-52" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.3rb33zto25.webp" loading="lazy" class="lazy"></p> <ul><li>主机驱动：为应用程序提供读写接口；和板载控制器通过 NVMe 等协议进行交互，完成应用程序的读写命令。</li> <li>板载控制器：
<ul><li>通过SATA、NVMe等协议，接收主机发送的命令并执行。</li> <li>管理SSD，实现FTL垃圾回收、磨损平衡等算法。</li> <li>控制和实现闪存时序。</li></ul></li> <li>闪存阵列：存储介质。</li></ul> <p>总体来说，Device Based 存在以下缺点：</p> <ol><li>FTL 架构通用，不能针对具体应用做定制化。</li> <li>控制器芯片功能复杂，设计难度大，研发成本高。</li> <li>闪存更新很快，一般每年闪存厂商都会推出新一代产品，有新的使用特性，需要控制器芯片做出修改，但是芯片改版成本很高。</li> <li>企业级应用需要高性能、大容量，通用控制器芯片支持的最大性能和容量有限制。</li> <li>企业级市场需求多种多样，有些需求需要控制器提供特殊功能支持，这些是通用 SSD 主控芯片无法提供的。</li></ol> <blockquote><p>为了解决这些问题，有些企业级 SSD 采用了 Host Based 方案。也有一些大型互联网公司，例如 Google、Microsoft，还有百度等，自己研发 Host BasedSSD，针对自己的存储架构，开发驱动程序和控制器逻辑。</p></blockquote> <h3 id="_4-10-2-host-based-ftl-架构"><a href="#_4-10-2-host-based-ftl-架构" class="header-anchor">#</a> 4.10.2 Host Based FTL 架构</h3> <p>Host Based SSD 一般的模式是把闪存的读写接口直接开放给驱动程序，这样驱动程序就能自行管理闪存内部资源。控制器大都采用可编程逻辑器件 FPGA，功能比较简单，主要实现 ECC 纠错和闪存时序控制。</p> <p>如图4-53所示，主机驱动直接管理闪存阵列，控制器只是起到 ECC 纠错算法和物理协议转换的作用。</p> <p><img alt="图4-53" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.9kg1danias.webp" loading="lazy" class="lazy"></p> <h3 id="_4-10-3-百度的软件定义闪存-sdf"><a href="#_4-10-3-百度的软件定义闪存-sdf" class="header-anchor">#</a> 4.10.3 百度的软件定义闪存 SDF</h3> <p>SDF 主要的特点有：</p> <ol><li>没有垃圾回收。SDF 的使用者使用闪存块大小的整数倍为单位来写数据（比如8 MB），所以每个闪存块里面不会有垃圾，或者整体都是垃圾，写之前直接擦除就可以了。这样的好处有：
<ul><li>SSD 内部不用做垃圾回收，读写带宽得到提高。</li> <li>不需要预留空间，释放出20%的额外空间。</li> <li>没有内部搬移数据产生的写操作，闪存没有了写放大，寿命延长。</li></ul></li> <li>没有闪存级 RAID。SSD 内部其实是闪存阵列，所以为了数据安全性，很多企业级 SSD 会用闪存组成 RAID 组，用一块或几块闪存保存 RAID 数据。但是互联网公司的数据一般都有3个备份，所以不担心 SSD 内部数据丢失，因此，RAID 是没有必要的。</li> <li>FPGA 作为控制芯片，功能很少：ECC、坏块管理、地址转换、动态磨损平衡。Virtex-5 FPGA 实现了 PCIe 接口和DMA, Spartan FPGA 则是闪存控制芯片。</li> <li>SSD 内部每个通道都向用户开放，由用户选择写哪个通道。</li> <li>软件接口层非常简单，相比传统的 Linux 存储堆栈，省略了文件系统、块设备、IO调度、SATA 协议等，用户直接通过 IOCTRL（设备驱动程序中对设备的 I/O 通道进行管理的函数）来发同步的写命令到PCIe驱动，如图4-57所示。软件延迟从12μs缩减到2～4μs，这个时间只是花在 PCIe 中断处理上。</li></ol> <p><img alt="图4-57" data-src="https://cdn.jsdelivr.net/gh/xiaose-code/picx-images-hosting@master/jianrong/image.2dojzyy3yj.webp" loading="lazy" class="lazy"></p></div></div> <!----> <div class="page-edit"><!----> <!----> <!----></div> <div class="page-nav-wapper"><div class="page-nav-centre-wrap"><a href="/pages/9a554c/" class="page-nav-centre page-nav-centre-prev"><div class="tooltip">第4章 SSD 主控</div></a> <a href="/pages/b2dae7/" class="page-nav-centre page-nav-centre-next"><div class="tooltip">第9章 ECC 原理</div></a></div> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/pages/9a554c/" class="prev">第4章 SSD 主控</a></span> <span class="next"><a href="/pages/b2dae7/">第9章 ECC 原理</a>→
      </span></p></div></div></div> <!----></main></div> <div class="footer"><!----> 
  Theme by
  <a href="https://github.com/xugaoyi/vuepress-theme-vdoing" target="_blank" title="本站主题">Vdoing</a> 
    | Copyright © 2023-2025
    <span>霜晨月</span></div> <div class="buttons"><div title="返回顶部" class="button blur go-to-top iconfont icon-fanhuidingbu" style="display:none;"></div> <div title="去评论" class="button blur go-to-comment iconfont icon-pinglun" style="display:none;"></div> <div title="主题模式" class="button blur theme-mode-but iconfont icon-zhuti"><ul class="select-box" style="display:none;"><li class="iconfont icon-zidong">
          跟随系统
        </li><li class="iconfont icon-rijianmoshi">
          浅色模式
        </li><li class="iconfont icon-yejianmoshi">
          深色模式
        </li><li class="iconfont icon-yuedu">
          阅读模式
        </li></ul></div></div> <!----> <!----> <!----></div><div class="global-ui"><canvas id="vuepress-canvas-cursor"></canvas></div></div>
    <script src="/assets/js/app.67adcfd9.js" defer></script><script src="/assets/js/4.9aaa1650.js" defer></script><script src="/assets/js/1.5474518c.js" defer></script><script src="/assets/js/3.593d14fc.js" defer></script><script src="/assets/js/132.1c04cde5.js" defer></script>
  </body>
</html>
